// Some easier XML-RPC methods for Mozilla.
// 12/7/2005, 26/12/2005, 6/1/2006 David Murray.
// http://deepestsender.mozdev.org/
// v0.3

// Modified  :
//   - Using Connection class
//   - Optimize for using with XMLRPC Calls.
// Notes : There's a bottle neck in the removeWhiteSpace function.
// It took several seconds to parse a medium size call (according to Venkman), causing a little hang
// on my 2.4GHz machine.

// Usage:
// makeXMLRpcCall(String methodname, Object rpcobj):
// Returns an XML document, ideally for sending through XMLHttpRequest().
//	Firstly, bunch up all the objects you want to send via XML-RPC into an array. Then use the makeXMLRpcCall() function to create
//	an XML document with everything. The first parameter to pass is the method name, the second parameter is the array you want to send.
//	XML document is returned, which you can send off via the usual XMLHttpRequest() method.

// parseXMLRpcResponse(Document obj):
// Returns an array (or object if faults are found), where each item corresponds to the item found in the response document.
//	Upon receiving a response from XMLHttpRequest(), run the .responseXML through the parseXMLRpcResponse() function. If no params tag is found, an
//	exception is thrown. Otherwise, an array/object will be the return value. If .faultCode or .faultString exist, then there was an error on the server side.
//	If not, the array returned will be full of all the objects returned from the response, in their proper JavaScript forms too.

// Strings are sent as <string>.
// Integers are sent as <i4> (and recognised if they're <i4> or <int>).
// Numbers with decimal notation are sent as <double>.
// Arrays are sent as <array>.
// Objects are sent as <struct>.
// Dates are sent as <dateTime.iso8601>.
// Booleans are sent as <boolean>.
// Implementation was made according to the spec at http://www.xmlrpc.com/spec

// Note: The idea was to base64 encode anything that wasn't recognised. This hasn't been written yet, so base64 isn't supported. I think there should also be a way
//	to override the type of object sent (ie. if you wanted to send a string as base64 or whatever). Other limitation is you can't send null values - it causes
//	the convertor to crap out.

// The aim of these two functions is to simplify XML-RPC in Mozilla to the point where it is trivial. The current implementation requires creating XPCOM objects
//	all over the place, won't automatically recognise object types, throws exceptions every 2 seconds, does a lot of other stuff that results in hair loss,
//	and in general just plain sucks horribly (well, I think it does anyway). I don't think anyone has actually ever tested it to see it works.

function makeXMLRpcCall(methodname, rpcarray) {
	this.xml = document.implementation.createDocument("", "methodCall", null);
	
	xml.insertBefore(xml.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\""), xml.documentElement);
	
	xml.documentElement.appendChild(xml.createElement("methodName"));
	xml.documentElement.firstChild.appendChild(xml.createTextNode(methodname));
	var params = xml.documentElement.appendChild(xml.createElement("params"));
    
	for (z = 0; z < rpcarray.length; z++) {
		var param = params.appendChild(xml.createElement("param"));
		var value = param.appendChild(xml.createElement("value"));
		value.appendChild(convertToXML(rpcarray[z]));
	}
	
	xml.sendRequest = function(url, callback) {
		try {
			var myConnection = new Connection(url);
			myConnection.setCallback(callback);
			myConnection.setErrorCallback(callback);
			myConnection.send(xml,"POST","xml");
			myConnection.xmlrpcMethodName = methodname;
			
			//Logger.verbose("XMLRPC call : \"" + methodname + "\"", rpcarray);
			return myConnection;
		}
		catch(e)
		{
			//Logger.error("XMLRPC call : \"" + methodname + "\" failed! Exception " + e, rpcarray);
			return false;
		}
	}
	
	return xml;
}

function parseResult(response)
{
	if (response.status > 0) {
		alert("XMLRPC call  \"" + response.methodName + "\" failed! Message : " + response.message);
		//Logger.error("XMLRPC call  \"" + response.methodName + "\" failed! Message : " + response.message, response);
		return null;
	}
		 
	var xml = response.content;
	
	try {
		var result = parseXMLRpcResponse(xml);
	} catch(e) {
		alert("XMLRPC method " + response.methodName + ": Cannot parse response : " + e);
		return null;
		//Logger.warning("XMLRPC method " + response.methodName + " : Cannot parse response : " + e);
	}
	
	if (result['faultCode']) {
		//alert("XMLRPC method " + response.methodName + ": Server returned an error code : " + result['faultCode'] + '\n' + result['faultString']);
		alert(result['faultString']);
		//Logger.warning("XMLRPC method " + response.methodName + ": Server returned an error code : " + result['faultCode'], result);
		return null;
	}
	
//	if (CONFIG.LOG_XMLRPC) {
		//Logger.verbose("XMLRPC response : " + response.methodName,result[0]);
//	}
		
	return result[0];
}

function convertToXML(obj) {
	var xml = document.implementation.createDocument("", "tempdoc", null);
	var findtype = new RegExp("function (.*?)\\(\\) \\{.*");
	switch (findtype.exec(obj.constructor.toString())[1]) {
		case "Number":
			// Numbers can only be sent as integers or doubles.
			if (Math.floor(obj) != obj) {
				var numtype = xml.createElement("double");
			} else {
				var numtype = xml.createElement("i4");
			}
			var number = xml.documentElement.appendChild(numtype);
			number.appendChild(xml.createTextNode(obj));
			break;
		case "String":
			var string = xml.documentElement.appendChild(xml.createElement("string"));
			string.appendChild(xml.createTextNode(obj));
			break;
		case "Boolean":
			var bool = xml.documentElement.appendChild(xml.createElement("boolean"));
			bool.appendChild(xml.createTextNode(obj * 1));
			break;
		case "Object":
			var struct = xml.documentElement.appendChild(xml.createElement("struct"));
			for (var w in obj) {
				if(typeof obj[y] == 'function')
					continue;
				var member = struct.appendChild(xml.createElement("member"));
				var membername = member.appendChild(xml.createElement("name"));
				membername.appendChild(xml.createTextNode(w));
				var value = member.appendChild(xml.createElement("value"));
				value.appendChild(convertToXML(obj[w]));
			}
			break;
		case "Date":
			var date = xml.documentElement.appendChild(xml.createElement("dateTime.iso8601"));
			var datetext = obj.getFullYear() + padNumber(obj.getMonth() + 1) + padNumber(obj.getDate())
				+ "T" + padNumber(obj.getHours()) + ":" + padNumber(obj.getMinutes()) + ":" + padNumber(obj.getSeconds());
			date.appendChild(xml.createTextNode(datetext));
			break;
		case "Array":
			var array = xml.documentElement.appendChild(xml.createElement("array"));
			var data = array.appendChild(xml.createElement("data"));
			for (var y in obj) {
				if(typeof obj[y] == 'function')
					continue;
				var value = data.appendChild(xml.createElement("value"));
				value.appendChild(convertToXML(obj[y]));
			}
			break;
		default:
			dump("\n" + obj + " - wtf is this data type you're spewing forth? It's been sent as a base64.");
			// Hellishly awful binary encoding shit goes here.
			break;
	}
	return xml.documentElement.firstChild;
}

function padNumber(num) {
	if (num < 10) {
		num = "0" + num;
	}
	return num;
}

function removeWhiteSpace(node) {

	var notWhitespace = /\S/
	for (var x = 0; x < node.childNodes.length; x++) {
		var childNode = node.childNodes[x];
		if ((childNode.nodeType == 3) && (!notWhitespace.test(childNode.textContent))) {
			// that is, if it's a whitespace text node
			node.removeChild(node.childNodes[x]);
			x--;
		}
		if (childNode.nodeType == 1) {
			// elements can have text child nodes of their own
			removeWhiteSpace(childNode);
		}
	}
	
}

function parseXMLRpcResponse(xml) {
	removeWhiteSpace(xml);
	var methodresponse = xml.documentElement;
	var rpcresponse = new Array();
	if (methodresponse.tagName != "methodResponse") {
		throw "No methodResponse found!";
	} else {
		var params = methodresponse.firstChild;
		if (params.tagName == "fault") {
			var faultstruct = params.firstChild.firstChild;
			return convertFromXML(faultstruct);
		} else {
			for (r = 0; r < params.childNodes.length; r++) {
				var value = params.childNodes[r].firstChild.firstChild;
				rpcresponse.push(convertFromXML(value));
			}
		}
	}
	return rpcresponse;
}

function convertFromXML(obj) {
	var data;
	switch (obj.tagName) {
		case "double":
		case "i4":
		case "int":
			var number = obj.textContent;
			data = number * 1;
			break;
		case "boolean":
			var bool = obj.textContent;
			if ((bool == "true") || (bool == "1")) {
				data = true;
			} else {
				data = false;
			}
			break;
		case "dateTime.iso8601":
			var date = obj.textContent;
			data = new Date();
			data.setFullYear(date.substring(0,4), date.substring(4,6) - 1, date.substring(6,8));
			data.setHours(date.substring(9,11), date.substring(12,14), date.substring(15,17));
			break;
		case "array":
			data = [];
			var datatag = obj.firstChild;
			for (var k = 0; k < datatag.childNodes.length; k++) {
				var value = datatag.childNodes[k];
				data.push(convertFromXML(value.firstChild));
			}
			break;
		case "struct":
			data = new Object();
			for (var j = 0; j < obj.childNodes.length; j++) {
				var membername = obj.childNodes[j].getElementsByTagName("name")[0].textContent;
				var membervalue = obj.childNodes[j].getElementsByTagName("value")[0].firstChild;
				if (membervalue) {
					data[membername] = convertFromXML(membervalue);
				} else {
					data[membername] = null;
				}
			}
			break;
		case "string":
		default:
			data = obj.textContent;
			break;
	}
	return data;
}

	/**
	 *
	function sendHttpRequest(url, request, callback_func, is_async) {
	    var http_req = new XMLHttpRequest();
	    var mode = request ? "POST" : "GET";
	    if(is_async == undefined) is_async = true;
	    http_req.open(mode, url, is_async);
	    if (mode == "POST") {
	        http_req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
	        // http_req.setRequestHeader("Content-Type", "text/xml");
	    }
	    
	    http_req.onreadystatechange = function () {
	
	        if (http_req.readyState == 4)
	        if (http_req.status == 200) {
	            callback_func(http_req.responseText,http_req.responseXML);
	        }
	        else alert("HTTP REQUEST FAILED WITH WRONG STATUS CODE");
	    };
	    http_req.send(request);
	    return http_req;
	}
	*/
